package dcli;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.List;

/**
 * Universidad Simón Bolívar
 * Curso de Redes I - Taller
 * Profesor: Ricardo Gonzalez
 * Trimestre Enero-Marzo 2011
 *
 * Proyecto 2 RD
 *
 * @author Grupo 9
 * @author Richard Simoes Ferreira
 * @author Ricardo Tocco
 *
 * @version 1.0
 *
 */

public class ControlDcli {

/**
 * Estructura de control para el programa dcli
 */
    
    private int puerto;
    private String servidorDrep;
    private String ldir;
    private String arch;
    private String username;
    private ClientConnection clienteDrep;
    private boolean sesionIniciada;

    /**
     * Constructor de la clase ControlDcli
     *
     * @param p     Puerto bien conocido de un drep
     * @param s     Ubicación de un servidor drep
     * @param ld    Camino o path dentro del sistema de archivo local
     * @param arch  Dirección absoluta o relativa de un archivo de comandos
     * @param usr   Nombre de usuario dcli
     * @param cli   Apuntador a la clase encargada de implementar
     *              la comunicación a través de sockets.
     */
    public ControlDcli (int p, String s, String ld, String arch, String usr, ClientConnection cli){
        this.puerto = p;
        this.servidorDrep = s;
        this.ldir = ld;
        this.arch = arch;
        this.username = usr;
        this.clienteDrep = cli;
        this.sesionIniciada = false;
    }

    /**
     * Obtener puerto del repositorio drep bien conocido
     * @return Puerto
     */
    public int getPuertoDrep(){
        return this.puerto;
    }

    /**
     * Establecer el puerto del repositorio drep bien conocido
     * @param p Puerto
     */
    public void setPuertoDrep(int p){
        this.puerto = p;
    }

    /**
     * Obtener nombre o dirección IPv4 del servidor drep
     * @return  nombre o dirección IPv4 del servidor drep
     */
    public String getServidorDrep(){
        return this.servidorDrep;
    }

    /**
     * Establecer el nombre o dirección IPv4 del servidor drep
     * @param s nombre o dirección IPv4 del servidor drep
     */
    public void setServidorDrep(String s){
        this.servidorDrep = s;
    }

    /**
     * Obtener el camino o path del directorio local dcli
     * @return  El camino o path del directorio local dcli
     */
    public String getLocalDir(){
        return this.ldir;
    }

    /**
     * Establecer el camino o path del directorio local dcli
     * @param d El camino o path del directorio local dcli
     */
    public void setLocalDir(String d){
        this.ldir = d;
    }

    /**
     * Obtener el nombre o dirección absoluta o relativa del archivo de comandos
     * @return  Nombre o dirección absoluta o relativa del archivo de comandos
     */
    public String getArchivo(){
        return this.arch;
    }

    /**
     * Obtiene todos los comandos que se encuentran dentro del archivo de
     * entrada arch
     * @return  Una lista con todos los comandos que se encuentran en el archivo
     */
    public List<String> getComandosArch(){
        List<String> comandos = new ArrayList<String>();
        BufferedReader br = null;
        ErrorHandler errorH = new ErrorHandler();

        try {
            br = new BufferedReader(new FileReader(this.arch));
            String comando= null;

            while ((comando = br.readLine()) != null) {
                comandos.add(comando);
            }

        } catch (IOException e) {
            errorH.errorLecturaArchivoComandos(e);
        }

        return comandos;
    }

    /**
     * Establecer la dirección absoluta o relativa del archivo de comandos
     * @param a nombre o dirección absoluta o relativa del archivo de comandos
     */
    public void setArchivo(String a){
        this.arch = a;
    }

    /**
     * Obtener el nombre de usuario del programa dcli
     * @return  Nombre de usuario del programa dcli
     */
    public String getUsuario(){
        return this.username;
    }

    /**
     * Establecer el nombre de usuario del programa dcli
     * @param u Nombre de usuario del programa dcli
     */
    public void setUsuario(String u){
        this.username = u;
    }

    /**
     * Obtener la estructura de comunicación del programa dcli con drep
     * @return  Estructura de comunicación del programa dcli con drep
     */
    public ClientConnection getClienteDrep(){
        return this.clienteDrep;
    }

    /**
     * Establecer la estructura de comunicación del programa dcli con drep
     * @param c Estructura de comunicación del programa dcli con drep
     */
    public void setClienteDrep(ClientConnection c){
        this.clienteDrep = c;
    }

    /**
     * Función que muestra en pantalla los nombres y tamaños de cada uno de los
     * archivos locales al programa dcli, es decir, de los archivos que estén
     * contenidos en el directorio ldir
     */
    public void ldir(){
        File  dir = new File(this.ldir);
        File [] files = dir.listFiles();
        ErrorHandler errorH = new ErrorHandler();

        if (files == null){
            //Directorio no existe o no es un directorio
            errorH.directorioNoEncontrado();
        } else {
            for (int i=0;i<files.length;i++){
                System.out.println(files[i].getName()+"\t"+files[i].length());
            }
        }
    }

    /**
     * Indicará en pantalla el número de todos los programas drep
     * que están activos en el sistema
     */
    public void nrep(){
        int status;
        String response = null;
        ErrorHandler errorH = new ErrorHandler(this);

        status = clienteDrep.sendLinea("nrep");
        if (status<0){
            errorH.errorSolicitudNrep();
            return;
        }

        response = clienteDrep.Getlinea();

        //Chequear mensaje de error Protocolo
        if (response.equals("error")){
            errorH.protocolErrorNrep();
            return;
        } else if (!response.equals("excepcion")) {
            System.out.println(response);
        } else {
            errorH.ErrorNrep();
        }
    }

    /**
     * Función que establece una sesión del programa dcli con su correspondiente
     * repositorio drep
     */
    public void iniciarSesion(){
        int status;
        String response = null;
        ErrorHandler errorH = new ErrorHandler(this);

        status = clienteDrep.sendLinea("newDcli"+"##%"+this.username+"##%");
        if (status<0){
            errorH.errorSolicitudIniciarSesion();
            return;
        }

        response = clienteDrep.Getlinea();
 
        if (response.equals("accept")){
            System.out.println("inicio de sesion exitoso");
            this.sesionIniciada = true;
        } else if (!response.equals("excepcion")){
            errorH.protocolErrorNewDcli();
            return;
        } else {
            errorH.ErrorNewDcli(this.clienteDrep);
            return;
        }

    }

    /**
     * Función que establece una sesión del programa dcli con su correspondiente
     * repositorio drep
     */
    public int cerrarSesion(){
        int status;
        String response = null;
        ErrorHandler errorH = new ErrorHandler(this);

        status = clienteDrep.sendLinea("disconnectDcli"+"##%"+this.username+"##%");
        if (status<0){
            errorH.errorSolicitudCerrarSesion();
            System.exit(-1);
        }

        response = clienteDrep.Getlinea();

        if (response.equals("accept")){
            System.out.println("Se ha cerrado la sesion exitosamente");
            this.sesionIniciada = false;
            return 0;
        } else if (!response.equals("excepcion")){
            errorH.protocolErrorDisconnectDcli();
            return -1;
        } else {
            errorH.ErrorDisconnectDcli();
            return -2;
        }

    }
    
    /**
     * Devuelve el estado de la sesion de un dcli su respectivo drep
     * @return  true si la sesion esta iniciada en caso contrario false
     */
    public boolean getSesionIniciada(){
        return this.sesionIniciada;
    }
    /**
     * Establece el estado de la sesion de un dcli con su respectivo drep
     * @param i Estado de la sesion, true si esta iniciada, false si no.
     */
    public void setSesionIniciada(boolean i){
        this.sesionIniciada = i;
    }
    

    /**
     * Lista por pantalla el contenido del archivo especificado.
     *
     * @param archivo   el nombre archivo del cual se obtendra el contenido
     *                  a mostrar.
     */
    protected void cat(String archivo) {
        ErrorHandler errorH = new ErrorHandler();
        try {
            Runtime rt = Runtime.getRuntime();
            String[] cmd = {"/bin/bash","-c","cat "+ldir+archivo};
            String cadena = "";
            Process pr = rt.exec(cmd);
            BufferedReader input =
                    new BufferedReader(new InputStreamReader(pr.getInputStream()));
            String line=null;

            while((line=input.readLine()) != null) {
                cadena += line + '\n';
            }

            System.out.println(cadena);

            int exitVal = pr.waitFor();
            if (exitVal != 0) {
                errorH.errorRetornoCat(archivo);
            }

        } catch(Exception e) {
            errorH.errorCat(e);
        }
    }

    /**
     * Sube el contenido del archivo al drep correspondiente.
     * Se envia al drep un String que contiene: el caracter s, para indicar que
     * se está realizando la operación de subir archivos, seguido de caracteres
     * separadores (##%), el nombre del archivo, seguido de caracteres
     * separadores (##%), y por último la longitud del archivo.
     *
     * @param archivo el nombre archivo que se transferirá al drep.
     */
    protected void sube(String nombreArchivo) {

        RandomAccessFile archivo = null;
        ErrorHandler errorH = new ErrorHandler();

        try{
            // Se abre el archivo a enviar en modo lectura
            archivo = new RandomAccessFile(ldir+nombreArchivo,"r");
            //Armamos la solicitud;
            String datos = "s##%"+nombreArchivo+"##%"+archivo.length();
            System.out.println("Inicia la transferencia del archivo...");
            clienteDrep.sendLinea(datos);
            String st = clienteDrep.Getlinea();
            if (st.equals("error")){
                errorH.errorTransferenciaIncompletaSube();
                return;
            }
                
            // Se realiza la transferencia del archivo byte por byte.
            while(archivo.getFilePointer()<archivo.length()) {
                byte dato = archivo.readByte();
                clienteDrep.sendByte(dato);
            }

            // En el servidor se realiza la comprobación con la cantidad de
            // bytes recibidos. Envía 1 si la transferencia se efectuó
            // correctamente; 0 en caso contrario.
            String s = clienteDrep.Getlinea();
            if (s.equals("1")) {
                System.out.println("La transferencia del archivo terminó " +
                        "satisfactoriamente...");
            }
            else if (s.equals("0")) {
                errorH.errorTransferenciaIncompletaSube();
            } else if (s.equals("excepcion")){
                return;
            }
        } catch(FileNotFoundException h) {
            errorH.errorArchivoNoExisteSube(h, nombreArchivo);
        } catch(IOException e) {
            errorH.errorTransferenciaSube(e);
        } finally {
            try {
                if (archivo != null) archivo.close();
            } catch (IOException ex) {
                errorH.errorTransferenciaIncompletaSube();
            }
        }

    }

    /**
     * Baja el contenido de un archivo en el drep correspondiente.
     * Se envia al drep un String que contiene: el caracter b, para indicar que
     * se está realizando la operación de bajar archivos, seguido de caracteres
     * separadores (##%) y el nombre del archivo que se desea descargar.
     * El servidor responde con el contenido de del archivo o un error en caso
     * de que el archivo no se encuentra en el servidor.
     *
     * @param archivo el nombre archivo que se transferirá al drep.
     */
    protected void baja(String nombreArchivo) {
        RandomAccessFile archivo = null;
        ErrorHandler errorH = new ErrorHandler();
        File archi;

        try{
            archi = new File(ldir+nombreArchivo);
            clienteDrep.sendLinea("b##%"+nombreArchivo);
            // Primero se recibe la cantidad de Bytes que se va a recibir.
            Long tamArchivo = clienteDrep.GetLong();
            if (tamArchivo >= 0 ) {
                // Se crea el archivo.
                if (archi.exists())
                    archi.delete();
                archivo = new RandomAccessFile(archi, "rw");
                clienteDrep.sendLinea("fino");
                int numBytes = 0;
                // Se inicia la descarga del archivo
                while(numBytes < tamArchivo) {
                    byte dato = clienteDrep.GetByte();
                    archivo.writeByte(dato);
                    numBytes++;
                }
                // Comprobación del archivo. Si se descargó correctamente se
                // se envía un número 1. De lo contrario un 0.
                if(numBytes == archivo.length()) {
                    clienteDrep.sendLinea("1");
                    System.out.println("La transferencia del archivo terminó" +
                            " satisfactoriamente...");
                } else{
                    clienteDrep.sendLinea("0");
                    errorH.errorTransferenciaIncompletaBaja();
                }

            } else {
                errorH.errorArchivoNoExisteBaja(nombreArchivo);
            }
        } catch(FileNotFoundException ex) {
            clienteDrep.sendLinea("error");
            errorH.errorArchivoNoExisteBaja(nombreArchivo);
        } catch (IOException ef) {
            errorH.errorTransferenciaBaja(ef);
        } finally {
            try {
                if (archivo != null) archivo.close();
            } catch (IOException ex) {
                errorH.errorTransferenciaIncompletaBaja();
            }
        }
    }

    /**
     * Muestra los archivos y tamaños de los mismos que se encuentran en el
     * drep correspondiente.
     */
    protected void rdir() {
        ErrorHandler errorH = new ErrorHandler();
        clienteDrep.sendLinea("rdir");
        String buf = clienteDrep.Getlinea();
        if (!buf.equals("vacio")&&!buf.equals("excepcion")) {
            String arreglo[] = buf.split("##%");
            for (int i = 0; i < arreglo.length; i++) {
                System.out.println(arreglo[i]);
            }
        } else if (!buf.equals("excepcion")){
            System.out.println("No hay archivos en el repositorio");
        } else {
           errorH.ErrorRdir();
        }
    }

    /**
     * Muestra en pantalla la lista de todos los usuarios activos en un tiempo
     * determinado
     */
    protected  void tuser() {
        ErrorHandler errorH = new ErrorHandler();
        clienteDrep.sendLinea("tuser");
        String str = clienteDrep.Getlinea();
        if (str.equals("noExistenUsuarios"))
            System.out.println("Actualmente no existen usuarios Dcli conectados");
        if (!str.equals("excepcion")){
            System.out.println("Usuarios Dcli conectados");
            System.out.println("-------------------");
            String[] arreglo = str.split("##%");
            for (int i = 0; i < arreglo.length; i++) {
                System.out.println(arreglo[i]);
            }
        } else {
            errorH.ErrorTuser();
        }
    }

}